﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Windows.Threading;
using System.Collections.ObjectModel;
using System.Windows.Media.Animation;

namespace WrWpf.Controls
{
    /// <summary>
    /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
    ///
    /// Step 1a) Using this custom control in a XAML file that exists in the current project.
    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
    /// to be used:
    ///
    ///     xmlns:MyNamespace="clr-namespace:WrWpf"
    ///
    ///
    /// Step 1b) Using this custom control in a XAML file that exists in a different project.
    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
    /// to be used:
    ///
    ///     xmlns:MyNamespace="clr-namespace:WrWpf;assembly=WrWpf"
    ///
    /// You will also need to add a project reference from the project where the XAML file lives
    /// to this project and Rebuild to avoid compilation errors:
    ///
    ///     Right click on the target project in the Solution Explorer and
    ///     "Add Reference"->"Projects"->[Browse to and select this project]
    ///
    ///
    /// Step 2)
    /// Go ahead and use your control in the XAML file.
    ///
    ///     <MyNamespace:CalendarMonth/>
    ///
    /// </summary>
    /// <remarks>
    /// The algorithm, which is used to convert lunar-solar calendar, is created by Ho Ngoc Duc.
    /// I just optimize some snippet code to gain performance.
    /// Website: http://www.informatik.uni-leipzig.de/~duc/amlich/calrules.html
    /// </remarks>
    [TemplatePart(Name = "PART_Panel", Type = typeof(Panel))]
    [TemplatePart(Name="PART_MonthName", Type=typeof(TextBlock))]
    [TemplatePart(Name="PART_Year", Type=typeof(TextBox))]
    [TemplatePart(Name="PART_UpDown", Type=typeof(StackPanel))]
    public class CalendarMonth : Control
    {
        #region Private members

        const double PI_6 = Math.PI / 6;
        const double PI_12 = Math.PI / 12;
        const double PI2 = Math.PI * 2;

        Panel pnl = null;
        int selectedDay = -1;
        DateTime selectedMonth;
        DispatcherTimer timer = null;

        private static readonly RoutedCommand DayClickedCommand;

        //Indicate how many days of the previous month we will fill in the calendar
        int startDistanceDay = 0;
        #endregion

        #region Commands

        public static readonly RoutedCommand PreviousYearCommand;
        public static readonly RoutedCommand NextYearCommand;
        public static readonly RoutedCommand ChangeMonthCommand;

        #endregion

        #region Constructors

        static CalendarMonth()
        {
            //Initialize Commands
            PreviousYearCommand = new RoutedCommand("PreviousYear", typeof(CalendarMonth));
            NextYearCommand = new RoutedCommand("NextYear", typeof(CalendarMonth));
            DayClickedCommand = new RoutedCommand("DayClicked", typeof(CalendarMonth));
            ChangeMonthCommand = new RoutedCommand("ChangeMonthCommand", typeof(CalendarMonth));
            //Initialize Properties
            DayTypeProperty = DependencyProperty.Register(
                "DayType",
                typeof(Type),
                typeof(CalendarMonth),
                new PropertyMetadata(typeof(CalendarDay), DayTypeChangedCallback),
                DayTypeValidate);

            FirstDayInWeekKey = DependencyProperty.RegisterReadOnly(
                "FirstDayInWeek",
                typeof(int),
                typeof(CalendarMonth),
                new PropertyMetadata(0));

            FirstDayInWeekProperty = FirstDayInWeekKey.DependencyProperty;

            AbbreviatedYearMonthKey = DependencyProperty.RegisterReadOnly(
                "AbbreviatedYearMonth",
                typeof(string),
                typeof(CalendarMonth),
                new PropertyMetadata());

            AbbreviatedYearMonthProperty = AbbreviatedYearMonthKey.DependencyProperty;

            AbbreviatedMonthNameKey = DependencyProperty.RegisterReadOnly(
                "AbbreviatedMonthName",
                typeof(string),
                typeof(CalendarMonth),
                new PropertyMetadata());

            AbbreviatedMonthNameProperty =            AbbreviatedMonthNameKey.DependencyProperty;
            
            YearKey = DependencyProperty.RegisterReadOnly(
                "Year",
                typeof(string),
                typeof(CalendarMonth),
                new PropertyMetadata());

            YearProperty = YearKey.DependencyProperty;
            
            MonthNameKey = DependencyProperty.RegisterReadOnly(
                "MonthName",
                typeof(string),
                typeof(CalendarMonth),
                new PropertyMetadata());

            MonthNameProperty = MonthNameKey.DependencyProperty;

            MonthNamesKey = DependencyProperty.RegisterReadOnly(
                "MonthNames",
                typeof(ObservableCollection<MenuItem>),
                typeof(CalendarMonth),
                new PropertyMetadata());

            MonthNamesProperty = MonthNamesKey.DependencyProperty;
            
            CultureProperty = DependencyProperty.Register(
                "Culture",
                typeof(CultureInfo),
                typeof(CalendarMonth),
                new PropertyMetadata(CultureChangedCallback));
            
            AbbreviatedDayNamesKey = DependencyProperty.RegisterReadOnly(
                "AbbreviatedDayNames",
                typeof(ObservableCollection<string>),
                typeof(CalendarMonth),
                new PropertyMetadata());

            AbbreviatedDayNamesProperty = AbbreviatedDayNamesKey.DependencyProperty;
            
            DayNamesKey = DependencyProperty.RegisterReadOnly(
                "DayNames",
                typeof(ObservableCollection<string>),
                typeof(CalendarMonth),
                new PropertyMetadata());

            DayNamesProperty = DayNamesKey.DependencyProperty;
            
            AutoUpdateTodayProperty = DependencyProperty.Register(
                "AutoUpdateToday",
                typeof(bool),
                typeof(CalendarMonth),
                new PropertyMetadata(AutoUpdateTodayCallback));
            
            ShowCheckboxesProperty = CalendarDay.ShowCheckboxesProperty.AddOwner(
                typeof(CalendarMonth),
                new PropertyMetadata(ShowCheckboxesCallback));
            
            DateProperty = CalendarDay.DateProperty.AddOwner(
                typeof(CalendarMonth),
                new PropertyMetadata(DateChangedCallback));

            ViewPostChangeAnimationProperty = DependencyProperty.Register(
                "ViewPostChangeAnimation", 
                typeof(Storyboard), 
                typeof(CalendarMonth), 
                new FrameworkPropertyMetadata(null));

            ViewPreChangeAnimationProperty = DependencyProperty.Register(
                "ViewPreChangeAnimation", 
                typeof(Storyboard), 
                typeof(CalendarMonth), 
                new FrameworkPropertyMetadata(null));

            FillFulLWeekProperty = DependencyProperty.Register(
                "FillFulLWeek",
                typeof(bool),
                typeof(CalendarMonth),
                new PropertyMetadata(true, RefreshCalendar));

            HolidaysProperty = DependencyProperty.Register(
                "SpecialDays",
                typeof(HolidayCollection),
                typeof(CalendarMonth),
                new PropertyMetadata(RefreshCalendar));

            //The first argument to OverrideMetadata is the type of the class modifying the metadata
            //the argument to the FrameworkPropertyMetadata constructor indicates the new default value 
            //of the DefaultStyleKey property, which is also the type of the class.
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CalendarMonth),
                new FrameworkPropertyMetadata(typeof(CalendarMonth)));
            //Map keys for routed commands
            PreviousYearCommand.InputGestures.Add(new KeyGesture(Key.PageUp, ModifierKeys.Shift));
            NextYearCommand.InputGestures.Add(new KeyGesture(Key.PageDown, ModifierKeys.Shift));
            DayClickedCommand.InputGestures.Add(new MouseGesture(MouseAction.LeftClick));

            //Register events
            CalendarMonth.SelectedDayChangedEvent = EventManager.RegisterRoutedEvent(
                "SelectedDayChanged",
                RoutingStrategy.Direct,
                typeof(RoutedEventHandler),
                typeof(CalendarMonth));
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CalendarMonth"/> class.
        /// </summary>
        public CalendarMonth()
        {
            // Bind commands to control.
            CommandBindings.Add(new CommandBinding(ComponentCommands.MoveToPageUp, PreviousMonthExecuted));
            CommandBindings.Add(new CommandBinding(ComponentCommands.MoveToPageDown, NextMonthExecuted));
            CommandBindings.Add(new CommandBinding(PreviousYearCommand, PreviousYearExecuted));
            CommandBindings.Add(new CommandBinding(NextYearCommand, NextYearExecuted));

            CommandBindings.Add(new CommandBinding(ComponentCommands.MoveUp, UpArrowKeyExecuted));
            CommandBindings.Add(new CommandBinding(ComponentCommands.MoveDown, DownArrowKeyExecuted));
            CommandBindings.Add(new CommandBinding(ComponentCommands.MoveLeft, LeftArrowKeyExecuted));
            CommandBindings.Add(new CommandBinding(ComponentCommands.MoveRight, RightArrowKeyExecuted));

            // Initialize some properties.
            this.Date = DateTime.Now;

            //Initialize special days
            this.Holidays = this.CreateVietnameseHolidays();
        }
        #endregion

        #region Private functions

        private HolidayCollection CreateVietnameseHolidays()
        {
            HolidayCollection holidays = new HolidayCollection();
            holidays.Add(new EventDay(1, 1, 0));           //Solar new year
            holidays.Add(new EventDay(1, 1, 0, -1, true));   //Lunar new year
            holidays.Add(new EventDay(1, 1, 0, true));     //Lunar new year
            holidays.Add(new EventDay(2, 1, 0, true));     //Lunar new year
            holidays.Add(new EventDay(3, 1, 0, true));     //Lunar new year
            holidays.Add(new EventDay(30, 4, 0));          //Southern freedom day
            holidays.Add(new EventDay(1, 5, 0));           //Working day
            holidays.Add(new EventDay(2, 9, 0));           //Dependant day
            holidays.Add(new EventDay(10, 3, 0, true));    //Hung Vuong

            return holidays;
        }

        /// <summary>
        /// TimerOnTick sets IsToday propery of CalendarDay children
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void TimerOnTick(object sender, EventArgs args)
        {
            if (pnl != null)
            {
                //foreach (UIElement el in pnl.Children)
                //{
                //    CalendarDay calday = el as CalendarDay;

                //    if (calday != null)
                //        calday.IsToday = calday.Date.Date == DateTime.Today;
                //}
                DateTime today = DateTime.Today;
                if (this.Date.Day != today.Day) //Day is changed. Refresh calendar.
                {
                    //Make sure that the previous date can not be selected
                    if (this.Date.Month == today.Month && this.Date.Year == today.Year)
                        (pnl.Children[this.Date.Day - 1] as CalendarDay).IsToday = false;
                    int dayInMonth = today.Day - 1;    //base on zero
                    (pnl.Children[dayInMonth] as CalendarDay).IsToday = true;
                    this.Date = today;
                }
            }
        }

        /// <summary>
        /// CreateDaysInPanel finds panel and creates CalendarDay objects
        /// </summary>
        private void CreateDaysInPanel()
        {
            CultureInfo info = Culture ?? CultureInfo.InvariantCulture;
            DateTimeFormatInfo dtinfo = info.DateTimeFormat;

            if (this.Template != null)
            {
                pnl = this.GetTemplateChild("PART_Panel") as Panel;

                if (pnl != null)
                {
                    //Whether we fill days of other months.
                    bool fillFullWeek = this.FillFullWeek;

                    PreChangeAnimate();

                    pnl.Children.Clear();

                    // Set CalendarDay dates to the DateTime at noon.
                    DateTime dateOnFirst = new DateTime(Date.Year, Date.Month, 1, 0, 0, 0);
                    int dayInMonth = DateTime.DaysInMonth(Date.Year, Date.Month);
                    int endDistanceDay = dayInMonth;
                    if (fillFullWeek)
                    {
                        FirstDayInWeek = 0;
                        startDistanceDay = ((int)dtinfo.FirstDayOfWeek - (int)(dateOnFirst.DayOfWeek) - 7) % 7;
                        int missingDays = (dayInMonth + (int)dateOnFirst.DayOfWeek) % 7;
                        if (missingDays != 0)
                            missingDays = 7 - missingDays;
                        endDistanceDay += missingDays;
                    }
                    else
                        FirstDayInWeek = ((int)(dateOnFirst.DayOfWeek) - (int)dtinfo.FirstDayOfWeek + 7) % 7;
                    //ConstructorInfo cnst = DayType.GetConstructor(new Type[] { typeof(DateTime) });
                    HolidayCollection specialDays = this.Holidays;

                    for (int i = startDistanceDay; i < endDistanceDay; i++)
                    {
                        //CalendarDay day = cnst.Invoke(new object[] { dateOnFirst.AddDays(i) }) as CalendarDay;
                        CalendarDay day = new CalendarDay(dateOnFirst.AddDays(i));
                        if (day.Date == DateTime.Today)
                            day.IsToday = true;
                        //ToolTip tt = (ToolTip)SharedDictionaryManager.GetResourceDictionary(SharedDictionaryManager.CalendarDayStyle)["toolTipCalendarDay"];
                        //day.ToolTip = tt;
                        day.ShowCheckboxes = this.ShowCheckboxes;
                        day.CommandBindings.Add(new CommandBinding(DayClickedCommand, DayClickedExecuted));
                        day.IsEnabled = (i >= 0 && i < dayInMonth);
                        day.IsHoliday = specialDays.IsHoliday(new EventDay(day.Date)) ||
                            specialDays.IsHoliday(new EventDay(day.LunarDate));
                        pnl.Children.Add(day);
                    }

                    if (this.selectedDay > -1 && 
                        dateOnFirst.Month == selectedMonth.Month && dateOnFirst.Year == selectedMonth.Year)
                        (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = true;

                    PostChangeAnimate();
                }
            }
        }

        /// <summary>
        /// Convert Gregorian date (the current using calendar system) to Julian day number
        /// </summary>
        /// <param name="dt">The date will be converted.</param>
        /// <returns></returns>
        internal static int Gregorius2Julius(DateTime dt)
        {
            return Gregorius2Julius(dt.Year, dt.Month, dt.Day);
        }

        /// <summary>
        /// Convert Gregorian date (the current using calendar system) to Julian day number
        /// </summary>
        /// <param name="year">The year.</param>
        /// <param name="month">The month.</param>
        /// <param name="day">The day.</param>
        /// <returns></returns>
        private static int Gregorius2Julius(int year, int month, int day)
        {
            int a = (14 - month) / 12;
            int y = year + 4800 - a;
            int m = month + (a << 3) + (a << 2) - 3; //month + 12 * a - 3

            //int jd = day + (153 * m + 2) / 5 + 365 * y + (y >> 2) - y / 100 + y / 400 - 32045;
            //if (jd < 2299161)
            //    jd = day + (153 * m + 2) / 5 + 365 * y + (y >> 2) - 32083;
            int jd = day + ((m << 7) + (m << 4) + (m << 3) + m + 2) / 5 + ((y << 8) + (y << 6) + (y << 5) + (y << 3) + (y << 2) + y) + (y >> 2) - y / 100 + y / 400 - 32045;
            if (jd < 2299161)
                jd = day + ((m << 7) + (m << 4) + (m << 3) + m + 2) / 5 + ((y << 8) + (y << 6) + (y << 5) + (y << 3) + (y << 2) + y) + (y >> 2) - 32083;

            return jd;
        }

        /// <summary>
        /// Get Gregorian date from Julian day number
        /// </summary>
        /// <param name="jd">The jd.</param>
        /// <returns></returns>
        private static DateTime Julius2Gregorius(int jd)
        {
            int b, c;
            if (jd > 2299160) // After 5/10/1582, Gregorian calendar
            {
                var a = jd + 32044;
                b = ((a << 2) + 3) / 146097;
                c = a - ((b * 146097) >> 2);
            }
            else
            {
                b = 0;
                c = jd + 32082;
            }
            int d = ((c << 2) + 3) / 1461;
            int e = c - ((1461 * d) >> 2);
            int m = ((e << 2) + e + 2) / 153;  //(5 * e + 2) / 153
            int day = e - ((m << 7) + (m << 4) + (m << 3) + m + 2) / 5 + 1;    //e - (153 * m + 2) / 5 + 1
            int m10 = m / 10;
            int month = m + 3 - (m10 << 3) - (m10 << 2); //m+3-12*(m/10)
            int year = (b << 6) + (b << 5) + (b << 2) + d - 4800 + m10;    //b * 100 + d - 4800 + m / 10
            return new DateTime(year == 0 ? 4 : year, month, day);  //trick
        }

        /// <summary>
        /// Compute the time of the k-th new moon (Sóc) after the new moon of 1/1/1900 13:52 UCT.
        /// (measured as the number of days since 1/1/4713 BC noon UCT, e.g., 2451545.125 is 1/1/2000 15:00 UTC).
        /// </summary>
        /// <param name="k">The k-th new moon.</param>
        /// <returns>
        /// Returns a floating number, e.g., 2415079.9758617813 for k=2 or 2414961.935157746 for k=-2
        /// </returns>
        /// <remarks>
        /// Algorithm from: "Astronomical Algorithms" by Jean Meeus, 1998
        /// </remarks>
        private static double GetNewMoon(int k)
        {
            double t = (double)k / 1236.85; // Time in Julian centuries from 1900 January 0.5
            double t2 = t * t;
            double t3 = t2 * t;
            double dr = Math.PI / 180;
            double jd1 = 2415020.75933 + 29.53058868 * k + 0.0001178 * t2 - 0.000000155 * t3;
            jd1 = jd1 + 0.00033 * Math.Sin((166.56 + 132.87 * t - 0.009173 * t2) * dr); // Mean new moon
            double m = 359.2242 + 29.10535608 * k - 0.0000333 * t2 - 0.00000347 * t3; // Sun's mean anomaly
            double mpr = 306.0253 + 385.81691806 * k + 0.0107306 * t2 + 0.00001236 * t3; // Moon's mean anomaly
            double f = 21.2964 + 390.67050646 * k - 0.0016528 * t2 - 0.00000239 * t3; // Moon's argument of latitude
            double m_dr = m * dr;
            double mpr_dr = mpr * dr;
            double mpr_dr_2 = mpr_dr * 2;
            double f_dr = f * dr;
            double f_dr_2 = f_dr * 2;
            double c1 = (0.1734 - 0.000393 * t) * Math.Sin(m_dr) + 0.0021 * Math.Sin(m_dr * 2);
            c1 = c1 - 0.4068 * Math.Sin(mpr_dr) + 0.0161 * Math.Sin(mpr_dr_2);
            c1 = c1 - 0.0004 * Math.Sin(mpr_dr + mpr_dr_2);
            c1 = c1 + 0.0104 * Math.Sin(f_dr_2) - 0.0051 * Math.Sin(m_dr + mpr_dr);
            c1 = c1 - 0.0074 * Math.Sin(m_dr - mpr_dr) + 0.0004 * Math.Sin(f_dr_2 + m_dr);
            c1 = c1 - 0.0004 * Math.Sin(f_dr_2 - m_dr) - 0.0006 * Math.Sin(f_dr_2 + mpr_dr);
            c1 = c1 + 0.0010 * Math.Sin(f_dr_2 - mpr_dr) + 0.0005 * Math.Sin(mpr_dr_2 + m_dr);
            double deltat;
            if (t < -11)
                //deltat = 0.001 + 0.000839 * t + 0.0002261 * t2 - 0.00000845 * t3 - 0.000000081 * t * t3;
                deltat = 0.001 + 0.000839 * t + 0.0002261 * t2 - t3 * (0.00000845 + 0.000000081 * t);
            else
                deltat = -0.000278 + 0.000265 * t + 0.000262 * t2;
            return jd1 + c1 - deltat;
        }

        /// <summary>
        /// Compute the day of the k-th new moon in the given time zone.
        /// </summary>
        /// <param name="k">The k.</param>
        /// <param name="timeZone">The time zone if the time difference between local time and UTC: 7.0 for UTC+7:00</param>
        /// <returns></returns>
        private static int GetNewMoonDay(int k, double timeZone)
        {
            return (int)Math.Floor(GetNewMoon(k) + .5 + timeZone / 24);
        }

        /// <summary>
        /// Find the day that starts the luner month 11 of the given year for the given time zone
        /// </summary>
        /// <param name="year">The year.</param>
        /// <param name="timeZone">The time zone if the time difference between local time and UTC: 7.0 for UTC+7:00</param>
        /// <returns></returns>
        private static int GetLunarMonth11(int year, double timeZone)
        {
            int k, nm, sunLong;
            //off = jdFromDate(31, 12, yy) - 2415021.076998695;
            double off = Gregorius2Julius(year, 12, 31) - 2415021.076998695;
            k = (int)Math.Floor(off / 29.530588853);
            nm = GetNewMoonDay(k, timeZone);
            sunLong = GetSunLongitude(nm, timeZone); // sun longitude at local midnight
            if (sunLong >= 9)
            {
                nm = GetNewMoonDay(k - 1, timeZone);
            }
            return nm;
        }

        /// <summary>
        /// Find the index of the leap month after the month starting on the day a11.
        /// </summary>
        /// <param name="a11">The a11.</param>
        /// <param name="timeZone">The time zone.</param>
        /// <returns></returns>
        private static int GetLeapMonthOffset(int a11, double timeZone)
        {
            int k, last, arc, i;
            k = (int)Math.Floor((a11 - 2415021.076998695) / 29.530588853 + 0.5);
            last = 0;
            i = 1; // We start with the month following lunar month 11
            arc = GetSunLongitude(GetNewMoonDay(k + i, timeZone), timeZone);
            do
            {
                last = arc;
                i++;
                arc = GetSunLongitude(GetNewMoonDay(k + i, timeZone), timeZone);
            } while (arc != last && i < 14);
            return i - 1;
        }

        /// <summary>
        /// Compute the longitude of the sun at any time.
        /// </summary>
        /// <param name="jdn">the number of days since 1/1/4713 BC noon</param>
        /// <returns></returns>
        /// <remarks>Algorithm from: "Astronomical Algorithms" by Jean Meeus, 1998</remarks>
        private static double GetSunLongitude(double jdn)
        {
            double T, T2, dr, M, L0, DL, L;
            T = (jdn - 2451545.0) / 36525; // Time in Julian centuries from 2000-01-01 12:00:00 GMT
            T2 = T * T;
            dr = Math.PI / 180; // degree to radian
            //M = 357.52910 + 35999.05030 * T - 0.0001559 * T2 - 0.00000048 * T * T2; // mean anomaly, degree
            M = 357.52910 + 35999.05030 * T - T2 * (0.0001559 + 0.00000048 * T);
            L0 = 280.46645 + 36000.76983 * T + 0.0003032 * T2; // mean longitude, degree
            double dr_m = dr * M;
            DL = (1.914600 - 0.004817 * T - 0.000014 * T2) * Math.Sin(dr_m);
            double dr_m_2 = dr_m * 2;
            DL = DL + (0.019993 - 0.000101 * T) * Math.Sin(dr_m_2) + 0.000290 * Math.Sin(dr_m + dr_m_2);
            L = L0 + DL; // true longitude, degree
            L = L * dr;
            return L - PI2 * (Math.Floor(L / PI2)); // Normalize to (0, 2*PI)
        }

        /// <summary>
        /// Compute sun position at midnight (Trung khí) of the day with the given Julian day number. 
        /// The time zone if the time difference between local time and UTC: 7.0 for UTC+7:00.
        /// </summary>
        /// <param name="jdn">The JDN.</param>
        /// <param name="timeZone">The time zone if the time difference between local time and UTC: 7.0 for UTC+7:00</param>
        /// <returns>The function returns a number between 0 and 11.</returns>
        /// <remarks>
        /// From the day after March equinox and the 1st major term after March equinox, 0 is returned.
        /// After that, return 1, 2, 3 ...
        /// </remarks>
        private static int GetSunLongitude(int dayNumber, double timeZone)
        {
            return (int)Math.Floor(GetSunLongitude((double)dayNumber - 0.5 - timeZone / 24) / PI_6);
        }

        /// <summary>
        /// Compute sun position at midnight (Trung khí) of the day with the given Julian day number. 
        /// The time zone if the time difference between local time and UTC: 7.0 for UTC+7:00.
        /// </summary>
        /// <param name="jdn">The JDN.</param>
        /// <param name="timeZone">The time zone if the time difference between local time and UTC: 7.0 for UTC+7:00</param>
        /// <returns>The function returns a number between 0 and 23.</returns>
        /// <remarks>
        /// From the day after March equinox and the 1st major term after March equinox, 0 is returned.
        /// After that, return 1, 2, 3 ...
        /// </remarks>
        internal static int GetSunLongitude24(int dayNumber, double timeZone)
        {
            return (int)Math.Floor(GetSunLongitude((double)dayNumber - 0.5 - timeZone / 24) / PI_12);
        }

        /// <summary>
        /// Comvert solar date dd/mm/yyyy to the corresponding lunar date.
        /// </summary>
        /// <param name="dt">The dt.</param>
        /// <param name="timeZone">The time zone.</param>
        /// <returns></returns>
        internal static LunarDateTime Solar2Lunar(DateTime dt, double timeZone)
        {
            int k, dayNumber, monthStart, a11, b11, lunarDay, lunarMonth, lunarYear, lunarLeap;
            int diff, leapMonthDiff;
            int yy = dt.Year;
            dayNumber = Gregorius2Julius(dt);
            k = (int)Math.Floor((dayNumber - 2415021.076998695) / 29.530588853);
            monthStart = GetNewMoonDay(k + 1, timeZone);
            if (monthStart > dayNumber)
            {
                monthStart = GetNewMoonDay(k, timeZone);
            }
            a11 = GetLunarMonth11(yy, timeZone);
            b11 = a11;
            if (a11 >= monthStart)
            {
                lunarYear = yy;
                a11 = GetLunarMonth11(yy - 1, timeZone);
            }
            else
            {
                lunarYear = yy + 1;
                b11 = GetLunarMonth11(yy + 1, timeZone);
            }
            lunarDay = dayNumber - monthStart + 1;
            diff = (int)Math.Floor((double)(monthStart - a11) / 29);
            lunarLeap = 0;
            lunarMonth = diff + 11;
            if (b11 - a11 > 365)
            {
                leapMonthDiff = GetLeapMonthOffset(a11, timeZone);
                if (diff >= leapMonthDiff)
                {
                    lunarMonth = diff + 10;
                    if (diff == leapMonthDiff)
                    {
                        lunarLeap = 1;
                    }
                }
            }
            if (lunarMonth > 12)
            {
                lunarMonth = lunarMonth - 12;
            }
            if (lunarMonth >= 11 && diff < 4)
            {
                lunarYear -= 1;
            }
            //return new Array(lunarDay, lunarMonth, lunarYear, lunarLeap);
            return new LunarDateTime(lunarYear, lunarMonth, lunarDay, lunarLeap == 1, timeZone);
        }

        /// <summary>
        /// Convert a lunar date to the corresponding solar date
        /// </summary>
        /// <param name="dt">The dt.</param>
        /// <param name="timeZone">The time zone.</param>
        /// <returns></returns>
        internal static DateTime Lunar2Solar(int lunarYear, int lunarMonth, int lunarDay, bool lunarLeap, double timeZone)
        {
            int k, a11, b11, off, leapOff, leapMonth, monthStart;
            if (lunarMonth < 11)
            {
                a11 = GetLunarMonth11(lunarYear - 1, timeZone);
                b11 = GetLunarMonth11(lunarYear, timeZone);
            }
            else
            {
                a11 = GetLunarMonth11(lunarYear, timeZone);
                b11 = GetLunarMonth11(lunarYear + 1, timeZone);
            }
            k = (int)Math.Floor(0.5 + (a11 - 2415021.076998695) / 29.530588853);
            off = lunarMonth - 11;
            if (off < 0)
            {
                off += 12;
            }
            if (b11 - a11 > 365)
            {
                leapOff = GetLeapMonthOffset(a11, timeZone);
                leapMonth = leapOff - 2;
                if (leapMonth < 0)
                {
                    leapMonth += 12;
                }
                if (lunarLeap && lunarMonth != leapMonth)
                {
                    return DateTime.MinValue;
                }
                else if (lunarLeap || off >= leapOff)
                {
                    off += 1;
                }
            }
            monthStart = GetNewMoonDay(k + off, timeZone);
            return Julius2Gregorius(monthStart + lunarDay - 1);
        }
        #endregion

        #region Override functions
        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal processes call <see cref="M:System.Windows.FrameworkElement.ApplyTemplate"/>.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            this.CreateDaysInPanel();

            TextBlock txt = this.GetTemplateChild("PART_MonthName") as TextBlock;
            if (txt != null)
            {
                txt.MouseLeftButtonDown += new MouseButtonEventHandler(txt_MouseLeftButtonDown);

                txt.ContextMenu.CommandBindings.Add(
                    new CommandBinding(ChangeMonthCommand, ChangeMonthExecuted));
            }

            TextBox txtYear = this.GetTemplateChild("PART_Year") as TextBox;
            if (txtYear != null)
            {
                txtYear.MouseDoubleClick += new MouseButtonEventHandler(txtYear_MouseDoubleClick);
                txtYear.LostFocus += new RoutedEventHandler(txtYear_LostFocus);
                txtYear.TextChanged += new TextChangedEventHandler(txtYear_TextChanged);
                txtYear.PreviewKeyDown += new KeyEventHandler(txtYear_PreviewKeyDown);
                StackPanel panel = (StackPanel)this.GetTemplateChild("PART_UpDown");
                Button btn = panel.Children[0] as Button;
                btn.LostFocus += txtYear_LostFocus;
                btn.Click += new RoutedEventHandler(btn_Click);
                btn = panel.Children[1] as Button;
                btn.LostFocus += txtYear_LostFocus;
                btn.Click += btn_Click;
            }
        }

        void txtYear_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            int keyCode = (int)e.Key;
            if ((keyCode >= (int)Key.D0 && keyCode <= (int)Key.D9) ||
                keyCode == (int)Key.Left || keyCode == (int)Key.Right ||
                keyCode == (int)Key.Home || keyCode == (int)Key.End ||
                keyCode == (int)Key.Back || keyCode == (int)Key.Delete
                )
            {
                if (((TextBox)sender).Text.Length > 0)
                    ((TextBox)sender).Tag = ((TextBox)sender).Text;
            }
            else
                e.Handled = true;
        }

        void txtYear_TextChanged(object sender, TextChangedEventArgs e)
        {
            TextBox txt = (TextBox)sender;
            if (txt.IsFocused)  //change by user
            {
                int year;
                if (int.TryParse(txt.Text, out year))
                {
                    if (1 <= year && year <= 9999)
                    {
                        if (year != this.Date.Year)
                        {
                            int lastDay = DateTime.DaysInMonth(year, this.Date.Month);
                            if (lastDay > this.Date.Day)
                                lastDay = this.Date.Day;
                            this.Date = new DateTime(year, this.Date.Month, lastDay);
                        }
                        return;
                    }

                }
                if (txt.Text.Length > 0)
                {
                    int start = txt.SelectionStart;
                    txt.Text = (string)txt.Tag;
                    txt.SelectionStart = start;
                }
            }
        }

        void btn_Click(object sender, RoutedEventArgs e)
        {
            Button btn = (Button)sender;
            int distanceYear = btn.Name.Equals("YearIncrease") ? 1 : -1;
            this.Date = this.Date.AddYears(distanceYear);
        }

        void txtYear_LostFocus(object sender, RoutedEventArgs e)
        {
            StackPanel panel = (StackPanel)this.GetTemplateChild("PART_UpDown");
            TextBox txtYear = this.GetTemplateChild("PART_Year") as TextBox;
            if (!panel.IsMouseOver && !txtYear.IsMouseOver)
            {
                txtYear.Focusable = false;
                txtYear.Cursor = Cursors.Arrow;
                txtYear.IsReadOnly = true;
                txtYear.BorderThickness = new Thickness(0);
                txtYear.Margin = new Thickness(0);
                if (txtYear.Text.Length == 0)
                    txtYear.Text = (string)txtYear.Tag;

                panel.Visibility = Visibility.Collapsed;
            }
        }

        void txtYear_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            TextBox txtYear = (TextBox)sender;
            txtYear.Focusable = true;
            txtYear.Cursor = Cursors.IBeam;
            txtYear.IsReadOnly = false;
            txtYear.BorderThickness = new Thickness(1.0);
            txtYear.Margin = new Thickness(4.0, 0, 0, 0);
            txtYear.Focus();

            StackPanel panel = (StackPanel)this.GetTemplateChild("PART_UpDown");
            panel.Visibility = Visibility.Visible;
        }

        void ChangeMonthExecuted(object sender, ExecutedRoutedEventArgs args)
        {
            TextBlock txt = this.GetTemplateChild("PART_MonthName") as TextBlock;
            MenuItem mItem = (MenuItem)args.OriginalSource;
            int index = 0;
            for (; index < txt.ContextMenu.Items.Count; index++)
            {
                if (mItem.Equals(txt.ContextMenu.Items[index]))
                    break;
            }
            index++;
            if (this.Date.Month != index)
            {
                int lastDay = DateTime.DaysInMonth(this.Date.Year, index);
                if (lastDay > this.Date.Day)
                    lastDay = this.Date.Day;
                this.Date = new DateTime(this.Date.Year, index, lastDay);
            }
        }

        /// <summary>
        /// Handles the MouseLeftButtonDown event of the txt control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        void txt_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            ((TextBlock)sender).ContextMenu.IsOpen = true;
        }
        #endregion

        #region Executed methods associated with RoutedCommand objects
        void PreviousMonthExecuted(object sender, ExecutedRoutedEventArgs args)
        {
            Date = Date.AddMonths(-1);
        }

        void NextMonthExecuted(object sender, ExecutedRoutedEventArgs args)
        {
            Date = Date.AddMonths(1);
        }

        void PreviousYearExecuted(object sender, ExecutedRoutedEventArgs args)
        {
            Date = Date.AddYears(-1);
        }

        void NextYearExecuted(object sender, ExecutedRoutedEventArgs args)
        {
            Date = Date.AddYears(1);
        }

        void UpArrowKeyExecuted(object sender, ExecutedRoutedEventArgs args)
        {
            if (selectedDay == -1)
                selectedDay = this.Date.Day - 1;

            if (selectedDay >= 7)
            {
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = false;
                selectedDay -= 7;
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = true;

                //Raise SelectedDayChanged event
                this.OnSelectedDayChanged();
            }
            else if (selectedMonth.Month != this.Date.Month || selectedMonth.Year != this.Date.Year)
            {
                this.selectedMonth = this.Date;
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = true;

                //Raise SelectedDayChanged event
                this.OnSelectedDayChanged();
            }
        }

        void DownArrowKeyExecuted(object sender, ExecutedRoutedEventArgs args)
        {
            if (selectedDay == -1)
                selectedDay = this.Date.Day - 1;
            DateTime today = this.Date;
            if (selectedDay + 7 < (new DateTime(today.Year, today.Month, 1)).AddMonths(1).AddDays(-1).Day)
            {
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = false;
                selectedDay += 7;
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = true;

                //Raise SelectedDayChanged event
                this.OnSelectedDayChanged();
            }
            else if (selectedMonth.Month != this.Date.Month || selectedMonth.Year != this.Date.Year)
            {
                this.selectedMonth = this.Date;
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = true;

                //Raise SelectedDayChanged event
                this.OnSelectedDayChanged();
            }
        }

        void LeftArrowKeyExecuted(object sender, ExecutedRoutedEventArgs args)
        {
            if (selectedDay == -1)
                selectedDay = this.Date.Day - 1;
            if (selectedDay > 0)
            {
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = false;
                selectedDay --;
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = true;

                //Raise SelectedDayChanged event
                this.OnSelectedDayChanged();
            }
            else if (selectedMonth.Month != this.Date.Month || selectedMonth.Year != this.Date.Year)
            {
                this.selectedMonth = this.Date;
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = true;

                //Raise SelectedDayChanged event
                this.OnSelectedDayChanged();
            }
        }

        void RightArrowKeyExecuted(object sender, ExecutedRoutedEventArgs args)
        {
            if (selectedDay == -1)
                selectedDay = this.Date.Day - 1;
            DateTime today = this.Date;
            if (selectedDay  < (new DateTime(today.Year, today.Month, 1)).AddMonths(1).AddDays(-2).Day)
            {
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = false;
                selectedDay++;
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = true;

                //Raise SelectedDayChanged event
                this.OnSelectedDayChanged();
            }
            else if (selectedMonth.Month != this.Date.Month || selectedMonth.Year != this.Date.Year)
            {
                this.selectedMonth = this.Date;
                if (selectedDay - startDistanceDay >= pnl.Children.Count)
                    selectedDay = DateTime.DaysInMonth(this.Date.Year, this.Date.Month) - 1;
                (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = true;

                //Raise SelectedDayChanged event
                this.OnSelectedDayChanged();
            }
        }

        void DayClickedExecuted(object sender, ExecutedRoutedEventArgs args)
        {
            CalendarDay calendarDay = args.Source as CalendarDay;
            if (calendarDay != null)
            {
                //We just perform if the selected date is different than the previous selected date
                int newSelectedDay = calendarDay.Date.Day - 1;
                if (newSelectedDay != selectedDay)
                {
                    //Make sure that we will clear the previous selected date
                    if (selectedDay != -1)
                    {
                        (pnl.Children[selectedDay - startDistanceDay] as CalendarDay).IsSelectedDay = false;
                    }
                    //Set the selected date
                    calendarDay.IsSelectedDay = true;
                    selectedDay = newSelectedDay;
                    selectedMonth = calendarDay.Date;

                    //Raise SelectedDayChanged event
                    this.OnSelectedDayChanged();
                }
            }
            if (/*this.Focusable &&*/ !this.IsFocused)
                this.Focus();
        }
        #endregion

        #region Properties

        #region ViewPreChangeAnimation
        public static readonly DependencyProperty ViewPreChangeAnimationProperty ;

        /// <summary>
        /// View change animation.
        /// </summary>
        public Storyboard ViewPreChangeAnimation
        {
            get { return (Storyboard)GetValue(ViewPreChangeAnimationProperty); }
            set { SetValue(ViewPreChangeAnimationProperty, value); }
        }

        /// <summary>
        /// Pres the change animate.
        /// </summary>
        private void PreChangeAnimate()
        {
            if (ViewPreChangeAnimation != null)
                ViewPreChangeAnimation.Begin(pnl);
        }

        #endregion

        #region ViewPostChangeAnimation
        public static readonly DependencyProperty ViewPostChangeAnimationProperty;

        /// <summary>
        /// View change animation.
        /// </summary>
        public Storyboard ViewPostChangeAnimation
        {
            get { return (Storyboard)GetValue(ViewPostChangeAnimationProperty); }
            set { SetValue(ViewPostChangeAnimationProperty, value); }
        }

        /// <summary>
        /// Posts the change animate.
        /// </summary>
        private void PostChangeAnimate()
        {
            if (ViewPostChangeAnimation != null)
                ViewPostChangeAnimation.Begin(pnl);
        }

        #endregion

        #region FillFullWeek
        public static readonly DependencyProperty FillFulLWeekProperty;

        /// <summary>
        /// Gets or sets a value indicating whether we fill empty cell by days of other months..
        /// </summary>
        /// <value><c>true</c> if [fill full week]; otherwise, <c>false</c>.</value>
        public bool FillFullWeek
        {
            get { return (bool)GetValue(FillFulLWeekProperty); }
            set { this.SetValue(FillFulLWeekProperty, value); }
        }

        /// <summary>
        /// Fills the full week callback.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        static void RefreshCalendar(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            ((CalendarMonth)obj).CreateDaysInPanel();
        }
        #endregion

        #region DayType
        public static readonly DependencyProperty DayTypeProperty;

        /// <summary>
        /// Gets or sets the type of the day.
        /// </summary>
        /// <value>The type of the day.</value>
        public Type DayType
        {
            set { SetValue(DayTypeProperty, value); }
            get { return (Type)GetValue(DayTypeProperty); }
        }

        static bool DayTypeValidate(object obj)
        {
            return typeof(CalendarDay).IsAssignableFrom(obj as Type);
        }

        static void DayTypeChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            (obj as CalendarMonth).CreateDaysInPanel();
        }
        #endregion

        #region FirstDayInWeek
        static readonly DependencyPropertyKey FirstDayInWeekKey;

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty FirstDayInWeekProperty;

        /// <summary>
        /// Gets or sets the first day in week.
        /// </summary>
        /// <value>The first day in week.</value>
        public int FirstDayInWeek
        {
            protected set { SetValue(FirstDayInWeekKey, value); }
            get { return (int)GetValue(FirstDayInWeekProperty); }
        }
        #endregion

        #region AbbreviatedYearMonth
        static readonly DependencyPropertyKey AbbreviatedYearMonthKey ;

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty AbbreviatedYearMonthProperty;

        /// <summary>
        /// Gets or sets the abbreviated year month.
        /// </summary>
        /// <value>The abbreviated year month.</value>
        public string AbbreviatedYearMonth
        {
            protected set { SetValue(AbbreviatedYearMonthKey, value); }
            get { return (string)GetValue(AbbreviatedYearMonthProperty); }
        }
        #endregion

        #region AbbreviatedMonthName
        static readonly DependencyPropertyKey AbbreviatedMonthNameKey ;

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty AbbreviatedMonthNameProperty;

        /// <summary>
        /// Gets or sets the name of the abbreviated month.
        /// </summary>
        /// <value>The name of the abbreviated month.</value>
        public string AbbreviatedMonthName
        {
            protected set { SetValue(AbbreviatedMonthNameKey, value); }
            get { return (string)GetValue(AbbreviatedMonthNameProperty); }
        }
        #endregion

        #region Year
        static readonly DependencyPropertyKey YearKey ;

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty YearProperty ;

        /// <summary>
        /// Gets or sets the year.
        /// </summary>
        /// <value>The year.</value>
        public string Year
        {
            protected set { SetValue(YearKey, value); }
            get { return (string)GetValue(YearProperty); }
        }
        #endregion

        #region MonthNames
        static readonly DependencyPropertyKey MonthNamesKey;

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty MonthNamesProperty;

        /// <summary>
        /// Gets or sets the name of the months.
        /// </summary>
        /// <value>The name of the months.</value>
        public ObservableCollection<MenuItem> MonthNames
        {
            protected set { SetValue(MonthNamesKey, value); }
            get { return (ObservableCollection<MenuItem>)GetValue(MonthNamesProperty); }
        }
        #endregion

        #region MonthName
        static readonly DependencyPropertyKey MonthNameKey ;

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty MonthNameProperty ;

        /// <summary>
        /// Gets or sets the name of the month.
        /// </summary>
        /// <value>The name of the month.</value>
        public string MonthName
        {
            protected set { SetValue(MonthNameKey, value); }
            get { return (string)GetValue(MonthNameProperty); }
        }
        #endregion

        #region Culture
        public static readonly DependencyProperty CultureProperty ;

        /// <summary>
        /// Gets or sets the culture.
        /// </summary>
        /// <value>The culture.</value>
        [TypeConverter(typeof(CultureInfoConverter))]
        public CultureInfo Culture
        {
            set 
            {
                if (!this.Culture.Equals(value))
                    SetValue(CultureProperty, value);
            }
            get { return (CultureInfo)GetValue(CultureProperty); }
        }

        static void CultureChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            (obj as CalendarMonth).CultureChangedCallback(args);
        }

        void CultureChangedCallback(DependencyPropertyChangedEventArgs args)
        {
            CultureInfo info = Culture ?? CultureInfo.CurrentCulture;
            DateTimeFormatInfo dtinfo = info.DateTimeFormat;

            //Day
            if (this.AbbreviatedDayNames == null)
            {
                this.DayNames = new ObservableCollection<string>();
                this.AbbreviatedDayNames = new ObservableCollection<string>();
            }

            bool add = this.AbbreviatedDayNames.Count != 7;

            for (int i = 0; i < 7; i++)
            {
                int index = (i + (int)dtinfo.FirstDayOfWeek) % 7;
                if (add)
                {
                    this.DayNames.Add(dtinfo.DayNames[index]);
                    this.AbbreviatedDayNames.Add(dtinfo.AbbreviatedDayNames[index]);
                }
                else
                {
                    this.DayNames[i] = dtinfo.DayNames[index];
                    this.AbbreviatedDayNames[i] = dtinfo.AbbreviatedDayNames[index];
                }
            }

            //Month
            if (this.MonthNames == null)
                this.MonthNames = new ObservableCollection<MenuItem>();

            add = this.MonthNames.Count!=12;

            for (int i = 1; i <= 12; i++)
            {
                if (add)
                {
                    MenuItem mItem = new MenuItem();
                    mItem.Header = dtinfo.GetMonthName(i);
                    mItem.Command = ChangeMonthCommand;

                    this.MonthNames.Add(mItem);
                }
                else
                {
                    this.MonthNames[i - 1].Header = dtinfo.GetMonthName(i);
                }
            }

            DateChangedCallback(args);
        }
        #endregion

        #region AbbreviatedDayNames
        static readonly DependencyPropertyKey AbbreviatedDayNamesKey ;

        public static readonly DependencyProperty AbbreviatedDayNamesProperty;

        /// <summary>
        /// Gets or sets the abbreviated day names.
        /// </summary>
        /// <value>The abbreviated day names.</value>
        public ObservableCollection<string> AbbreviatedDayNames
        {
            protected set { SetValue(AbbreviatedDayNamesKey, value); }
            get { return (ObservableCollection<string>)GetValue(AbbreviatedDayNamesProperty); }
        }
        #endregion

        #region DayNames
        static readonly DependencyPropertyKey DayNamesKey ;

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty DayNamesProperty ;

        /// <summary>
        /// Gets or sets the day names.
        /// </summary>
        /// <value>The day names.</value>
        public ObservableCollection<string> DayNames
        {
            protected set { SetValue(DayNamesKey, value); }
            get { return (ObservableCollection<string>)GetValue(DayNamesProperty); }
        }
        #endregion

        #region SpecialDays
        public static readonly DependencyProperty HolidaysProperty;

        public HolidayCollection Holidays
        {
            get { return (HolidayCollection)this.GetValue(HolidaysProperty); }
            set { this.SetValue(HolidaysProperty, value); }
        }
        #endregion

        #region AutoUpdateToday
        private static readonly DependencyProperty AutoUpdateTodayProperty;

        /// <summary>
        /// Gets or sets a value indicating whether [auto update today].
        /// </summary>
        /// <value><c>true</c> if [auto update today]; otherwise, <c>false</c>.</value>
        public bool AutoUpdateToday
        {
            get { return (bool)this.GetValue(AutoUpdateTodayProperty); }
            set { this.SetValue(AutoUpdateTodayProperty, value); }
        }

        static void AutoUpdateTodayCallback(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            bool autoUpdate = (bool)args.NewValue;
            CalendarMonth calendarMonth = (CalendarMonth)obj;
            if (autoUpdate)
            {
                // Set time for updating IsToday property in CalendarDay.
                if (calendarMonth.timer == null)
                {
                    calendarMonth.timer = new DispatcherTimer();

                    calendarMonth.timer.Interval = TimeSpan.FromSeconds(1);
                }
                calendarMonth.timer.Tick += calendarMonth.TimerOnTick;
                calendarMonth.timer.Start();
            }
            else
            {
                calendarMonth.timer.Stop();
                calendarMonth.timer.Tick -= calendarMonth.TimerOnTick;
            }
        }
        #endregion

        #region ShowCheckboxes
        public static readonly DependencyProperty ShowCheckboxesProperty;

        /// <summary>
        /// Gets or sets the show checkboxes.
        /// </summary>
        /// <value>The show checkboxes.</value>
        public Visibility ShowCheckboxes
        {
            get { return (Visibility)this.GetValue(ShowCheckboxesProperty); }
            set { this.SetValue(ShowCheckboxesProperty, value); }
        }

        static void ShowCheckboxesCallback(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            CalendarMonth calendarMonth = (CalendarMonth)obj;
            if (calendarMonth.pnl != null)
            {
                Visibility showCheckboxes = calendarMonth.ShowCheckboxes;
                foreach (UIElement uiElement in calendarMonth.pnl.Children)
                {
                    CalendarDay calendarDay = (CalendarDay)uiElement;
                    calendarDay.ShowCheckboxes = showCheckboxes;
                }
            }
        }
        #endregion

        #region SelectedDate
        //public static readonly DependencyProperty SelectedDateProperty;

        /// <summary>
        /// Gets or sets the selected date.
        /// </summary>
        /// <value>The selected date.</value>
        public DateTime SelectedDate
        {
            get
            {
                if (this.selectedDay == -1)
                    return DateTime.Today;
                else
                {
                    DateTime dt = this.Date;
                    return new DateTime(dt.Year, dt.Month, this.selectedDay + 1);
                }
            }
        }
        #endregion

        #region Date
        //If you need to define a property that is already defined by some other class but that 
        //you do not inherit from Control, don't entirely redefine the property. Instead, call AddOwner 
        //on the existing dependency property. Save the return value from AddOwner as a public static 
        //read-only field and use that value in the CLR property.
        public static readonly DependencyProperty DateProperty;

        public DateTime Date
        {
            get 
            { 
                return (DateTime)this.GetValue(DateProperty); 
            }
            set 
            {
                this.SetValue(DateProperty, value);
            }
        }

        /// <summary>
        /// This function will be called when we set the current date of CalendarMonth
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        static void DateChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {

            ((CalendarMonth)obj).DateChangedCallback(args);
        }

        /// <summary>
        /// This function will be called when we set the current date of CalendarMonth
        /// </summary>
        /// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        private void DateChangedCallback(DependencyPropertyChangedEventArgs args)
        {
            if (args.Property == DateProperty)
            {
                // Refresh calendar if only the day of the month has changed.
                DateTime oldDate = (DateTime)args.OldValue;
                DateTime newDate = (DateTime)args.NewValue;
                if (oldDate.Year == newDate.Year && oldDate.Month == newDate.Month)
                {
                    //Refresh calendar and return
                    (pnl.Children[oldDate.Day - 1] as CalendarDay).IsToday = false;
                    (pnl.Children[newDate.Day - 1] as CalendarDay).IsToday = true;
                    return;
                }
            }

            //Case: change culture 
            CultureInfo culture = this.Culture ?? CultureInfo.InvariantCulture;
            DateTimeFormatInfo dtInfo = culture.DateTimeFormat;

            Year = this.Date.Year.ToString();
            int month = this.Date.Month;
            MonthName = dtInfo.GetMonthName(month);
            AbbreviatedMonthName = dtInfo.GetAbbreviatedMonthName(month);
            AbbreviatedYearMonth = this.Date.Year.ToString();

            this.CreateDaysInPanel();

        }

        #endregion

        #endregion

        #region RoutedEvents

        #region SelectedDayChanged
        private static readonly RoutedEvent SelectedDayChangedEvent;

        /// <summary>
        /// Occurs when selected day changed.
        /// </summary>
        public event RoutedEventHandler SelectedDayChanged
        {
            add { AddHandler(SelectedDayChangedEvent, value); }
            remove { RemoveHandler(SelectedDayChangedEvent, value); }
        }

        /// <summary>
        /// Called when selected day changed.
        /// </summary>
        protected virtual void OnSelectedDayChanged()
        {
            RaiseEvent(new RoutedEventArgs(SelectedDayChangedEvent, this));
        }
        #endregion

        #endregion
    }

    public class HolidayCollection : ObservableCollection<EventDay>
    {
        public bool IsHoliday(EventDay day)
        {
            foreach (EventDay eventDay in this.Items)
            {
                if (eventDay.EqualDateMonth(day))
                    return true;
            }
            return false;
        }
    }

    public struct EventDay
    {
        public int Day;
        public int Month;
        public int Year;

        /// <summary>
        /// For some special holiday, we will take one or two day before a specific day.
        /// Example: in Lunar new year, we will take the day before 1/1, 1/1, 2/1 and 3/1.
        /// This field will define that distance :-)
        /// </summary>
        public int DistanceDay;
        public bool IsLunarDay;

        #region Constructors
        public EventDay(int day, int month, int year, int distanceDay, bool isLunarDay)
        {
            this.Day = day;
            this.Month = month;
            this.Year = year;
            this.DistanceDay = distanceDay;
            this.IsLunarDay = isLunarDay;
        }

        public EventDay(int day, int month, int year, bool isLunarDay) : this(day, month, year, 0, isLunarDay) { }

        public EventDay(int day, int month, int year) : this(day, month, year, 0, false) { }

        public EventDay(int day, int month, int year, int distanceDay) : this(day, month, year, distanceDay, false) { }

        public EventDay(DateTime dt)
            : this(dt.Day, dt.Month, dt.Year, 0, false)
        {
        }

        public EventDay(DateTime dt, int distanceDay) : this(dt.Day, dt.Month, dt.Year, distanceDay, false) { }

        public EventDay(LunarDateTime dt) : this(dt.Day, dt.Month, dt.Year, 0, true) { }

        public EventDay(LunarDateTime dt, int distanceDay) : this(dt.Day, dt.Month, dt.Year, distanceDay, true) { }
        #endregion

        #region Equals operator
        /// <summary>
        /// Compare whether the current instance equals to obj regardingless year.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public bool EqualDateMonth(EventDay obj)
        {
            int year = this.Year;
            this.Year = obj.Year;
            bool result = this.Equals(obj);
            this.Year = year;

            return result;
        }

        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <param name="obj">Another object to compare to.</param>
        /// <returns>
        /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
        /// </returns>
        public override bool Equals(object obj)
        {
            EventDay day = (EventDay)obj;

            DateTime dt1, dt2;

            //Now, we assume that there is no holiday in leap month
            //We just have only two holiday in lunar calendar: new year and Hung Vuong
            //Which can not be in leap month.
            if (this.IsLunarDay)
                dt1 = CalendarMonth.Lunar2Solar(day.Year, this.Month, this.Day, false, 7.0);
            else
                dt1 = new DateTime(day.Year == 0 ? 4 : day.Year, this.Month, this.Day);
            if (day.IsLunarDay)
                dt2 = CalendarMonth.Lunar2Solar(day.Year, day.Month, day.Day, false, 7.0);
            else
                dt2 = new DateTime(day.Year == 0 ? 4 : day.Year, day.Month, day.Day);

            dt1 = dt1.AddDays(this.DistanceDay);
            dt2 = dt2.AddDays(day.DistanceDay);

            return dt1 == dt2;
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="a">A.</param>
        /// <param name="b">The b.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator==(EventDay a, EventDay b)
        {
            return a.Equals(b);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="a">A.</param>
        /// <param name="b">The b.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator !=(EventDay a, EventDay b)
        {
            return !a.Equals(b);
        }
        #endregion
    }
}
